package com.fp.m7.client.presenter;

import net.customware.gwt.presenter.client.EventBus;
import net.customware.gwt.presenter.client.widget.WidgetDisplay;
import net.customware.gwt.presenter.client.widget.WidgetPresenter;

import com.allen_sauer.gwt.log.client.Log;
import com.fp.m7.client.AccountContext;
import com.fp.m7.client.MyMessages;
import com.fp.m7.client.common.IValidatableDisplay;
import com.fp.m7.client.event.InfoEvent;
import com.fp.m7.client.event.InfoEvent.UserMessageType;
import com.fp.m7.shared.Constants;
import com.fp.m7.shared.ValidationConstants;
import com.fp.m7.shared.ValidationResult;
import com.fp.m7.shared.dto.AccountDto;
import com.fp.m7.shared.services.AccountServiceAsync;
import com.fp.m7.shared.validator.NewPasswordsValidator;
import com.fp.m7.shared.validator.StatusAndDescriptionValidator;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.logical.shared.HasValueChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HasText;
import com.google.inject.Inject;

/**
 * The presenter that handles changing settings of the account - status, description, password, nickname, delete the
 * account, or upload an avatar picture.
 * 
 * @author 7
 */
public class SettingsPresenter extends WidgetPresenter<SettingsPresenter.Display> {

    public interface Display extends WidgetDisplay, IValidatableDisplay {

        // general:
        void setContent(AccountDto accountDto);

        // upload:
        void configureUploader(String uploadUrl, String[] validAvatarExtensions);

        void setUploadFinishedEvent(final Command event);

        // update status & description:
        HasText getStatus();

        HasText getDescription();

        HasClickHandlers getUpdateStatusAndDescriptionButton();

        // change nickname:
        HasText getNickname();

        HasValueChangeHandlers<String> getNewNicknameInput();

        HasClickHandlers getChangeNicknameButton();

        /** null --> checking availability, true --> available, false --> not available */
        void setNicknameAvailable(Boolean value);

        // change password:
        HasText getOldPassword();

        HasText getNewPassword();

        HasText getNewPasswordRepeated();

        HasClickHandlers getChangePasswordButton();

        void clearPasswords();

        // delete account:
        HasClickHandlers getDeleteAccountButton();

    }

    private static MyMessages msg = GWT.create(MyMessages.class);

    private AccountContext accountContext;
    private AccountServiceAsync accountService;
    private StatusAndDescriptionValidator validatorStatusAndDescription;
    private NewPasswordsValidator validatorNewPasswords;

    @Inject
    public SettingsPresenter(Display display, EventBus eventBus, //
            AccountContext accountContext, //
            StatusAndDescriptionValidator validatorStatusAndDescription, //

            NewPasswordsValidator validatorNewPasswords, //
            AccountServiceAsync accountService) {

        super(display, eventBus);

        this.accountContext = accountContext;
        this.validatorStatusAndDescription = validatorStatusAndDescription;
        this.validatorNewPasswords = validatorNewPasswords;
        this.accountService = accountService;
    }

    @Override
    protected void onBind() {
        display.configureUploader(Constants.AVATAR_UPLOAD_URL, Constants.AVATAR_VALID_EXTENSIONS);

        display.setUploadFinishedEvent(new Command() {
            @Override
            public void execute() {
                reloadAccount();
            }
        });

        display.getUpdateStatusAndDescriptionButton().addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent evt) {
                ValidationResult result = validatorStatusAndDescription.validate( //
                        display.getStatus().getText(), //
                        display.getDescription().getText());

                display.showErrors(result);
                if (result.isOk()) {
                    accountService.updateStatusAndDescription( //
                            display.getStatus().getText(), //
                            display.getDescription().getText(), //
                            updateStatusAndDescriptionCallback);
                }
            }
        });

        display.getNewNicknameInput().addValueChangeHandler(new ValueChangeHandler<String>() {
            @Override
            public void onValueChange(ValueChangeEvent<String> evt) {
                display.setNicknameAvailable(null); // set status to "checking availability.."

                if (display.getNickname().getText().matches(ValidationConstants.NICKNAME_REGEX)) {
                    // do not fire off the request instantly, but let's wait a second until user finishes typing
                    timer.schedule(1000);
                } else {
                    display.setNicknameAvailable(false);
                }
            }

            private Timer timer = new Timer() {
                @Override
                public void run() {
                    accountService.checkNicknameAvailability( //
                            display.getNickname().getText(), //
                            checkNicknameAvailabilityCallback);
                }
            };
        });

        display.getChangeNicknameButton().addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent evt) {
                accountService.changeNickname(display.getNickname().getText(), changeNicknameCallback);
                display.setNicknameAvailable(false);
            }
        });

        display.getChangePasswordButton().addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent evt) {
                String newPwd = display.getNewPassword().getText();
                String newPwdRpt = display.getNewPasswordRepeated().getText();
                String oldPwd = display.getOldPassword().getText();

                display.clearPasswords();

                ValidationResult result = validatorNewPasswords.validate(newPwd, newPwdRpt);
                display.showErrors(result);
                if (result.isOk()) {
                    accountService.changePassword(oldPwd, newPwd, changePasswordCallback);
                }
            }
        });

        display.getDeleteAccountButton().addClickHandler(new ClickHandler() {
            @Override
            public void onClick(ClickEvent evt) {
                accountService.deleteAccount(deleteAccountCallback);
            }
        });
    }

    @Override
    protected void onUnbind() {
    }

    @Override
    protected void onRevealDisplay() {
        display.setContent(accountContext.getAuthenticatedAccount());
    }

    private void reloadAccount() {
        accountService.getAuthenticatedAccount(reloadAccountCallback);
    }

    private AsyncCallback<AccountDto> reloadAccountCallback = new AsyncCallback<AccountDto>() {
        @Override
        public void onSuccess(AccountDto accountDto) {
            accountContext.setAuthenticatedAccount(accountDto);
            display.setContent(accountDto);
        }

        @Override
        public void onFailure(Throwable throwable) {
            accountContext.setAuthenticatedAccount(null);
            Log.info("Reloading authenticated account went wrong...", throwable);
        }
    };

    private AsyncCallback<Boolean> checkNicknameAvailabilityCallback = new AsyncCallback<Boolean>() {
        @Override
        public void onSuccess(Boolean result) {
            display.setNicknameAvailable(result);
        }

        @Override
        public void onFailure(Throwable throwable) {
            display.setNicknameAvailable(false);
            Log.info("Checking nickname availability went wrong...", throwable);
        }
    };

    private AsyncCallback<Boolean> changeNicknameCallback = new AsyncCallback<Boolean>() {
        @Override
        public void onSuccess(Boolean result) {
            if (result != null && result) {
                eventBus.fireEvent(new InfoEvent(msg.nicknameChangeSuccess(), UserMessageType.SUCCESS));
                reloadAccount();
            }
        }

        @Override
        public void onFailure(Throwable throwable) {
            Log.info("Changing nickname went wrong...", throwable);
        }
    };

    private AsyncCallback<Boolean> updateStatusAndDescriptionCallback = new AsyncCallback<Boolean>() {
        @Override
        public void onSuccess(Boolean result) {
            if (result != null && result) {
                eventBus.fireEvent(new InfoEvent(msg.statusAndDescriptionChangeSuccess(), UserMessageType.SUCCESS));
                reloadAccount();
            }
        }

        @Override
        public void onFailure(Throwable throwable) {
            Log.info("Updating status and description went wrong...", throwable);
        }
    };

    private AsyncCallback<ValidationResult> changePasswordCallback = new AsyncCallback<ValidationResult>() {
        @Override
        public void onSuccess(ValidationResult result) {
            if (result.isOk()) {
                eventBus.fireEvent(new InfoEvent(msg.passwordChangeSuccess(), UserMessageType.SUCCESS));
                reloadAccount();
            } else {
                display.showErrors(result);
            }
        }

        @Override
        public void onFailure(Throwable throwable) {
            Log.info("Changing password went wrong...", throwable);
        }
    };

    private AsyncCallback<Boolean> deleteAccountCallback = new AsyncCallback<Boolean>() {
        @Override
        public void onSuccess(Boolean result) {
            if (result != null && result) {
                accountContext.setAuthenticatedAccount(null);
                Window.Location.replace(Constants.LOG_OUT_URL);
            }
        }

        @Override
        public void onFailure(Throwable throwable) {
            Log.info("Deleting account went wrong...", throwable);
        }
    };
}
